/*
 * obj.c -- obj model loader
 *
 * Copyright (c) 2005-2007 David HENRY
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include "obj.h"

/**
 * Free resources allocated for the model.
 */
void gsubFreeOBJModel(obj_model_t *mdl) {
    int i;

    if (mdl) {
        if (mdl->vertices) {
            free(mdl->vertices);
            mdl->vertices = NULL;
        }

        if (mdl->texCoords[X]) {
            free(mdl->texCoords[X]);
            mdl->texCoords[X] = NULL;
        }

        if (mdl->normals[X]) {
            free(mdl->normals[X]);
            mdl->normals[X] = NULL;
        }

		if (mdl->normals[Y]) {
            free(mdl->normals[Y]);
            mdl->normals[Y] = NULL;
        }

        if (mdl->normals[Z]) {
            free(mdl->normals[Z]);
            mdl->normals[Z] = NULL;
        }

        if (mdl->faces) {
            for (i = 0; i < mdl->num_faces; ++i) {
                if (mdl->faces[i].vert_indices)
                    free(mdl->faces[i].vert_indices);

                if (mdl->faces[i].uvw_indices)
                    free(mdl->faces[i].uvw_indices);

                if (mdl->faces[i].norm_indices)
                    free(mdl->faces[i].norm_indices);
            }

            free(mdl->faces);
            mdl->faces = NULL;
        }

		if (mdl->indices) {
			free(mdl->indices);
			mdl->indices = NULL;
		}
    }
	return;
}

/**
 * Allocate resources for the model after first pass.
 */
static int malloc_model(obj_model_t *mdl) {
    if (mdl->num_verts) {
        mdl->vertices = (float4 *) calloc(mdl->num_verts, sizeof (float4));
        if (!mdl->vertices)
            return 0;
    }

    if (mdl->num_texCoords) {
        mdl->texCoords[X] = (float4 *) calloc(mdl->num_verts, sizeof (float4));
        if (!mdl->texCoords)
            return 0;
    }

    mdl->normals[X] = (float3 *) calloc(mdl->num_verts, sizeof (float3));
	mdl->normals[Y] = (float3 *) calloc(mdl->num_verts, sizeof (float3));
	mdl->normals[Z] = (float3 *) calloc(mdl->num_verts, sizeof (float3));

    if (mdl->num_faces) {
        mdl->faces = (struct obj_face_t *) calloc(mdl->num_faces, sizeof (struct obj_face_t));
        if (!mdl->faces)
            return 0;
    }

    return 1;
}

/**
 * Load an OBJ model from file -- first pass.
 * Get the number of triangles/vertices/texture coords for
 * allocating buffers.
 */
static int first_pass(FILE *fp, obj_model_t *mdl) {
    int v, t, n;
    char buf[256];

    while (!feof(fp)) {
        /* Read whole line */
        fgets(buf, sizeof (buf), fp);

        switch (buf[0]) {
            case 'v':
            {
                if (buf[1] == ' ') {
                    /* Vertex */
                    mdl->num_verts++;
                } else if (buf[1] == 't') {
                    /* Texture coords. */
                    mdl->num_texCoords++;
                } else if (buf[1] == 'n') {
                    /* Normal vector */
                    mdl->num_normals++;
                } else {
                    MGPI_PostMessagef("%s: warning: unknown token \"%s\" (ignoring)\n", __FUN__, buf);
                }

                break;
            }

            case 'f':
            {
                /* Face */
                if (sscanf(buf + 2, "%d/%d/%d", &v, &n, &t) == 3) {
                    mdl->num_faces++;
                    mdl->has_texCoords = 1;
                    mdl->has_normals = 1;
                } else if (sscanf(buf + 2, "%d//%d", &v, &n) == 2) {
                    mdl->num_faces++;
                    mdl->has_texCoords = 0;
                    mdl->has_normals = 1;
                } else if (sscanf(buf + 2, "%d/%d", &v, &t) == 2) {
                    mdl->num_faces++;
                    mdl->has_texCoords = 1;
                    mdl->has_normals = 0;
                } else if (sscanf(buf + 2, "%d", &v) == 1) {
                    mdl->num_faces++;
                    mdl->has_texCoords = 0;
                    mdl->has_normals = 0;
                } else {
                    /* Should never be there or the model is very crappy */
                    MGPI_PostMessagef("%s: found face with no vertex", __FUN__);
                }

                break;
            }

            case 'g':
            {
                /* Group */
                /*	fscanf (fp, "%s", buf); */
                break;
            }

            default:
                break;
        }
    }

    /* Check if informations are valid */
    if ((mdl->has_texCoords && !mdl->num_texCoords) ||
            (mdl->has_normals && !mdl->num_normals)) {
        MGPI_PostMessagef("%s: contradiction between collected info", __FUN__);
        return 0;
    }

    if (!mdl->num_verts) {
        MGPI_PostMessagef("%s: no vertex found", __FUN__);
        return 0;
    }
    /*
      printf ("first pass results: found\n");
      printf ("   * %i vertices\n", mdl->num_verts);
      printf ("   * %i texture coords.\n", mdl->num_texCoords);
      printf ("   * %i normal vectors\n", mdl->num_normals);
      printf ("   * %i faces\n", mdl->num_faces);
      printf ("   * has texture coords.: %s\n", mdl->has_texCoords ? "yes" : "no");
      printf ("   * has normals: %s\n", mdl->has_normals ? "yes" : "no");
     */
    return 1;
}

/**
 * Load an OBJ model from file -- first pass.
 * This time, read model data and feed buffers.
 */
static int second_pass(FILE *fp, obj_model_t *mdl) {
    float4 *pvert = mdl->vertices;
    float3 *puvw = mdl->texCoords[X];
    float3 *pnorm = mdl->normals[X];
    struct obj_face_t *pface = mdl->faces;
    char buf[128], *pbuf;
    int i;

    while (!feof(fp)) {
        /* Read whole line */
        fgets(buf, sizeof (buf), fp);

        switch (buf[0]) {
            case 'v':
            {
                if (buf[1] == ' ') {
                    /* Vertex - HERE!!!! */
                    if (sscanf(buf + 2, "%f %f %f %f",
                            &(*pvert)[0], &(*pvert)[1],
                            &(*pvert)[2], &(*pvert)[3]) != 4) {
                        if (sscanf(buf + 2, "%f %f %f", &(*pvert)[0],
                                &(*pvert)[1], &(*pvert)[2]) != 3) {
                            MGPI_PostMessagef("%s: error reading vertex data", __FUN__);
                            return 0;
                        } else {
                            (*pvert)[3] = 1.0;
                        }
                    }

                    pvert++;
                } else if (buf[1] == 't') {
                    /* Texture coords. - HERE!!!! */
                    if (sscanf(buf + 2, "%f %f %f", &(*puvw)[0],
                            &(*puvw)[1], &(*puvw)[2]) != 3) {
                        if (sscanf(buf + 2, "%f %f", &(*puvw)[0],
                                &(*puvw)[1]) != 2) {
                            if (sscanf(buf + 2, "%f", &(*puvw)[0]) != 1) {
                                MGPI_PostMessagef("%s: error reading texture coordinates", __FUN__);
                                return 0;
                            } else {
                                (*puvw)[1] = 0.0;
                            }
                        } else {
                            /*(*puvw)[2] = 0.0;*/
                        }
                    }

                    puvw++;
                } else if (buf[1] == 'n') {
                    /* Normal vector - HERE!!!! */
                    if (sscanf(buf + 2, "%f %f %f", &(*pnorm)[0],
                            &(*pnorm)[1], &(*pnorm)[2]) != 3) {
						MGPI_PostMessagef("%s: error reading normal vectors", __FUN__);
                        return 0;
                    }

                    pnorm++;
                }

                break;
            }

            case 'f':
            {
                pbuf = buf;
                pface->num_elems = 0;

                /* Count number of vertices for this face */
                while (*pbuf) {
                    if (*pbuf == ' ')
                        pface->num_elems++;

                    pbuf++;
                }

                /* Select primitive type */
                if (pface->num_elems < 3) {
                    MGPI_PostMessagef("%s: a face must have at least 3 vertices", __FUN__);
                    return 0;
                } else if (pface->num_elems == 3) {
                    pface->type = GL_TRIANGLES;
                } else if (pface->num_elems == 4) {
                    pface->type = GL_QUADS;
                } else {
                    pface->type = GL_POLYGON;
                }

                /* Memory allocation for vertices */
                pface->vert_indices = (int *) malloc(sizeof (int) * pface->num_elems);

                if (mdl->has_texCoords)
                    pface->uvw_indices = (int *) malloc(sizeof (int) * pface->num_elems);

                if (mdl->has_normals)
                    pface->norm_indices = (int *) malloc(sizeof (int) * pface->num_elems);

                /* Read face data */
                pbuf = buf;
                i = 0;

                for (i = 0; i < pface->num_elems; ++i) {
                    pbuf = strchr(pbuf, ' ');
                    pbuf++; /* Skip space */

                    /* Try reading vertices */
                    if (sscanf(pbuf, "%d/%d/%d",
                            &pface->vert_indices[i],
                            &pface->uvw_indices[i],
                            &pface->norm_indices[i]) != 3) {
                        if (sscanf(pbuf, "%d//%d", &pface->vert_indices[i],
                                &pface->norm_indices[i]) != 2) {
                            if (sscanf(pbuf, "%d/%d", &pface->vert_indices[i],
                                    &pface->uvw_indices[i]) != 2) {
                                sscanf(pbuf, "%d", &pface->vert_indices[i]);
                            }
                        }
                    }

                    /* Indices must start at 0 */
                    pface->vert_indices[i]--;

                    if (mdl->has_texCoords)
                        pface->uvw_indices[i]--;

                    if (mdl->has_normals)
                        pface->norm_indices[i]--;
                }

                pface++;
                break;
            }
        }
    }
    return 1;
}

/**
 * Load an OBJ model from file, in two passes.
 */
int gsubReadOBJModel(const char *filename, obj_model_t *mdl) {
	FILE *fp;
	int i, j = 0, i0, i1, i2, ind, vi0, vi1, vi2, ti0, ti1, ti2;
	float4 side1, side2, normal = {0,0,0,1}, tangent = {0,0,0,1}, binormal = {0,0,0,1};
	float2 tb1, tb2;
	float denom = 0.0f;

	if (mdl) {
		fp = fopen(filename, "r");
		if (!fp) {
			MGPI_PostMessagef("%s: couldn't open \"%s\"", __FUN__, filename);
			return 0;
		}

		/* first pass: read model info */
		if (!first_pass(fp, mdl)) {
			fclose(fp);
			return 0;
		}

		rewind(fp);

		/* memory allocation */
		if (!malloc_model(mdl)) {
			fclose(fp);
			gsubFreeOBJModel(mdl);
			return 0;
		}

		/* second pass: read model data */
		if (!second_pass(fp, mdl)) {
			fclose(fp);
			gsubFreeOBJModel(mdl);
			return 0;
		}

		/* collecting mesh indices */
		for (i = 0; i < mdl->num_faces; ++i) j += mdl->faces[i].num_elems;
		mdl->num_indices = j;

		mdl->indices = (GLuint *) calloc(mdl->num_indices, sizeof(GLuint));
	
		for (i = 0; i < mdl->num_faces; ++i)
			for (j = 0; j < mdl->faces[i].num_elems; ++j)
				mdl->indices[j] = mdl->faces[i].vert_indices[j];

		/* normals generation */
		if (mdl->gen_normals) {
			for (i = 0; i < mdl->num_faces; ++i) {

				i0 = mdl->faces[i].vert_indices[X];
				i1 = mdl->faces[i].vert_indices[Y];
				i2 = mdl->faces[i].vert_indices[Z];

				side1[X] = mdl->vertices[i0][X] - mdl->vertices[i2][X];
				side1[Y] = mdl->vertices[i0][Y] - mdl->vertices[i2][Y];
				side1[Z] = mdl->vertices[i0][Z] - mdl->vertices[i2][Z];
				side1[W] = 1.0f;

				side2[X] = mdl->vertices[i1][X] - mdl->vertices[i2][X];
				side2[Y] = mdl->vertices[i1][Y] - mdl->vertices[i2][Y];
				side2[Z] = mdl->vertices[i1][Z] - mdl->vertices[i2][Z];
				side2[W] = 1.0f;

				MGPI_CrossProduct(&normal, side1, side2);
				MGPI_Normalize(&normal);

				for (j = 0; j < mdl->faces[i].num_elems; ++j) {
					ind = mdl->faces[i].vert_indices[j];
					if (ind > -1 && ind < mdl->num_indices) {
						mdl->normals[X][ind][X] = normal[X];
						mdl->normals[X][ind][Y] = normal[Y];
						mdl->normals[X][ind][Z] = normal[Z];
					}
				}
			}
			mdl->has_normals = 1;
		}

		/* tangents and binormals generation */
		if (mdl->has_texCoords && mdl->has_normals)
			for (i = 0; i < mdl->num_faces; ++i) {

				i0 = mdl->faces[i].norm_indices[X];
				i1 = mdl->faces[i].norm_indices[Y];
				i2 = mdl->faces[i].norm_indices[Z];

				vi0 = mdl->faces[i].vert_indices[X];
				vi1 = mdl->faces[i].vert_indices[Y];
				vi2 = mdl->faces[i].vert_indices[Z];

				ti0 = mdl->faces[i].uvw_indices[X];
				ti1 = mdl->faces[i].uvw_indices[Y];
				ti2 = mdl->faces[i].uvw_indices[Z];

				side1[X] = mdl->vertices[vi0][X] - mdl->vertices[vi2][X];
				side1[Y] = mdl->vertices[vi0][Y] - mdl->vertices[vi2][Y];
				side1[Z] = mdl->vertices[vi0][Z] - mdl->vertices[vi2][Z];
				side1[W] = 1.0f;

				side2[X] = mdl->vertices[vi1][X] - mdl->vertices[vi2][X];
				side2[Y] = mdl->vertices[vi1][Y] - mdl->vertices[vi2][Y];
				side2[Z] = mdl->vertices[vi1][Z] - mdl->vertices[vi2][Z];
				side2[W] = 1.0f;

				tb1[X] = mdl->texCoords[X][ti0][X] - mdl->texCoords[X][ti2][X];
				tb1[Y] = mdl->texCoords[X][ti0][Y] - mdl->texCoords[X][ti2][Y];

				tb2[X] = mdl->texCoords[X][ti1][X] - mdl->texCoords[X][ti2][X];
				tb2[Y] = mdl->texCoords[X][ti1][Y] - mdl->texCoords[X][ti2][Y];

				denom = tb1[X] * tb2[Y] - tb2[X] * tb1[Y];

				if (denom != 0.0f) {
					tangent[X] = (tb2[Y] * side1[X] - tb1[Y] * side2[X]) / denom;
					tangent[Y] = (tb2[Y] * side1[Y] - tb1[Y] * side2[Y]) / denom;
					tangent[Z] = (tb2[Y] * side1[Z] - tb1[Y] * side2[Z]) / denom;
					tangent[W] = 1.0f;

					binormal[X] = (tb2[X] * side1[X] - tb1[X] * side2[X]) / denom;
					binormal[Y] = (tb2[X] * side1[Y] - tb1[X] * side2[Y]) / denom;
					binormal[Z] = (tb2[X] * side1[Z] - tb1[X] * side2[Z]) / denom;
					binormal[W] = 1.0f;

					MGPI_Normalize(&tangent);
					MGPI_Normalize(&binormal);

					/* tangent */
					mdl->normals[Y][i0][X] = tangent[X];
					mdl->normals[Y][i0][Y] = tangent[Y];
					mdl->normals[Y][i0][Z] = tangent[Z];

					mdl->normals[Y][i1][X] = tangent[X];
					mdl->normals[Y][i1][Y] = tangent[Y];
					mdl->normals[Y][i1][Z] = tangent[Z];

					mdl->normals[Y][i2][X] = tangent[X];
					mdl->normals[Y][i2][Y] = tangent[Y];
					mdl->normals[Y][i2][Z] = tangent[Z];

					/* binormal */
					mdl->normals[Z][i0][X] = -binormal[X];
					mdl->normals[Z][i0][Y] = -binormal[Y];
					mdl->normals[Z][i0][Z] = -binormal[Z];

					mdl->normals[Z][i1][X] = -binormal[X];
					mdl->normals[Z][i1][Y] = -binormal[Y];
					mdl->normals[Z][i1][Z] = -binormal[Z];

					mdl->normals[Z][i2][X] = -binormal[X];
					mdl->normals[Z][i2][Y] = -binormal[Y];
					mdl->normals[Z][i2][Z] = -binormal[Z];
				}

				/* distibuting the vectors across the face vertices */
				for (j = i2 + 1; j < mdl->faces[i].num_elems; ++j) {
					ind = mdl->faces[i].norm_indices[j];

					if (ind > -1 && ind < mdl->num_indices) {
						mdl->normals[Y][ind][X] = tangent[X];
						mdl->normals[Y][ind][Y] = tangent[Y];
						mdl->normals[Y][ind][Z] = tangent[Z];

						mdl->normals[Z][ind][X] = binormal[X];
						mdl->normals[Z][ind][Y] = binormal[Y];
						mdl->normals[Z][ind][Z] = binormal[Z];
					}
				}
			}

		fclose(fp);
		return 1;
	}
	return 0;
}

/**
 * Draw the OBJ model.
 */
void gsubRenderOBJModel(obj_model_t *mdl) {// TODO: use vertex arrays
    int i, j, ind;//, n;

	//glGetIntegerv(GL_MAX_TEXTURE_UNITS, &n);
	// Note: need to apply multitexture coordintes!!!!

    for (i = 0; i < mdl->num_faces; ++i) {
        glBegin(mdl->faces[i].type);
        {
			if (mdl->faces[i].num_elems >= 3)
				for (j = 0; j < mdl->faces[i].num_elems; ++j) {
					ind = mdl->faces[i].vert_indices[j];

					if (mdl->has_texCoords)
						glTexCoord3fv(mdl->texCoords[X][mdl->faces[i].uvw_indices[j]]);

					if (mdl->gen_normals) {
						if (!mdl->has_normals && ind > -1 && ind < mdl->num_indices) {
							//glTangent3fv(mdl->normals[Y][ind]);
							//glBinormal3fv(mdl->normals[Z][ind]);
							glNormal3fv(mdl->normals[X][ind]);
							glColor3fv(mdl->normals[Y][ind]);//tangent
							glSecondaryColor3fv(mdl->normals[Z][ind]);//binormal
						} else {
							//glTangent3fv(mdl->normals[Y][ind]);
							//glBinormal3fv(mdl->normals[Z][ind]);
							glNormal3fv(mdl->normals[X][mdl->faces[i].norm_indices[j]]);
							glColor3fv(mdl->normals[Y][mdl->faces[i].norm_indices[j]]);//tangent
							glSecondaryColor3fv(mdl->normals[Z][mdl->faces[i].norm_indices[j]]);//binormal
						}
						
					} else {
						if (mdl->has_normals) {
							//glTangent3fv(mdl->normals[Y][ind]);
							//glBinormal3fv(mdl->normals[Z][ind]);
							glNormal3fv(mdl->normals[X][mdl->faces[i].norm_indices[j]]);
							glColor3fv(mdl->normals[Y][mdl->faces[i].norm_indices[j]]);//tangent
							glSecondaryColor3fv(mdl->normals[Z][mdl->faces[i].norm_indices[j]]);//binormal
						}
					}

					glVertex4fv(mdl->vertices[mdl->faces[i].vert_indices[j]]);
				}
        }
        glEnd();
	}
	return;
}

void gsubMapAttributes(obj_model_t *mdl, float *attributes, ...)
{
	//?????
	return;
}

/*
void
init (const char *filename)
{
  GLfloat lightpos[] = { 5.0f, 10.0f, 0.0f, 1.0f };

  glClearColor (0.5f, 0.5f, 0.5f, 1.0f);
  glShadeModel (GL_SMOOTH);

  glEnable (GL_DEPTH_TEST);
  glEnable (GL_LIGHTING);
  glEnable (GL_LIGHT0);

  glLightfv (GL_LIGHT0, GL_POSITION, lightpos);

  if (!ReadOBJModel (filename, &objfile))
    exit (EXIT_FAILURE);
}

void
cleanup ()
{
  FreeModel (&objfile);
}

void
reshape (int w, int h)
{
  if (h == 0)
    h = 1;

  glViewport (0, 0, (GLsizei)w, (GLsizei)h);

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  gluPerspective (45.0, w/(GLdouble)h, 0.1, 1000.0);

  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();

  glutPostRedisplay ();
}

void
display ()
{
  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity ();

  glTranslatef (0.0f, 0.0f, -15.0f);

  RenderOBJModel (&objfile);

  glutSwapBuffers ();
}

void
keyboard (unsigned char key, int x, int y)
{
  if (key == 27)
    exit (0);
}

int
main (int argc, char *argv[])
{
  if (argc < 2)
    {
      fprintf (stderr, "usage: %s <filename.obj>\n", argv[0]);
      return -1;
    }

  glutInit (&argc, argv);
  glutInitDisplayMode (GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
  glutInitWindowSize (640, 480);
  glutCreateWindow ("OBJ Model");

  atexit (cleanup);
  init (argv[1]);

  glutReshapeFunc (reshape);
  glutDisplayFunc (display);
  glutKeyboardFunc (keyboard);

  glutMainLoop ();

  return 0;
}
 */
